/**
 * XQuery to Scala Translator
 * Fatemeh Borran-Dejnabadi
 * 9 November 2004
 */

package xquery2src;

import java.io._;

/** This class implements the scanner of the translator.
*/
class Scanner extends Tokens {

  /** The current token class.
   */
  var token:int = 0;

  /** The position of the first character of the current token.
   */
  var start:int = 0;

  /** The string representation of the current token. This variable
   *  is only set if the current token class has several possible
   *  textual representations.
   */
  var chars:String = "";

  /** A buffer for constructing string representations of tokens.
   */
  private var buf:StringBuffer = new StringBuffer();

  /** The current character.
   */
  private var ch:char = ' ';

  /** This character represents the end of the input.
   */
  final val EOF_CH = (-1).asInstanceOf[char];

  /** The line and the column of the current token.
   */
  private var line:int = 1;
  private var column:int = 0;

  /** The input stream providing the input characters.
   */
  final var in:InputStream = null;

  private var stringflag:boolean = true;

  /** close the file input stream
   */
  def close() = {
    in.close();
  }

  /** This method reads the next token and stores the token class
   *  in variable 'token'. If the token representation is not unique
   *  it will also leave a textual representation in variable 'chars'.
   *  Whitespaces and comments are skipped by this method.
   */
  def nextToken():Unit = {

    var flag:boolean = true;
    // skip comments and whitespace characters and replace
    while (flag) {
      // skip whitespace characters
      while (ch == ' ' || ch == '\t' || ch == '\f' || ch == '\n') // whitespace
      nextCh();
      // skip comments
      if (ch == '(') {
        nextCh();
        if (ch == ':') { // begin of comment
          nextCh();
          while (ch != ':' && ch != EOF_CH)
            nextCh();
          if (ch == ':') {
            nextCh();
            if (ch == ')') { // end of comment
              nextCh();
            }
          }
          else if (ch == EOF_CH) {
            Report.error("Unclosed Comment!");
          }
        }
        else { // '('
          token = LPAREN;
          return;
        }
      }
      else // if the next character is not comment or whitespace
        flag = false;
    }

    // initialize the position of the current token
    start = Position.encode(line, column);

    // read the current token
    token = readToken();
  }

  /** Read the next token, store its representation (if its not
   *  unique) in variable 'chars' and return the token class.
   */
  private def readToken():int = {
    
    // for all the identifiers and keywords
    if ((ch >= 'a' && ch <= 'z') ||
        (ch >= 'A' && ch <= 'Z') || ch == '*') {
	  buf.setLength(0);
	  buf.append(ch);
	  nextCh();
	  while ((ch >= 'a' && ch <= 'z') ||
		 (ch >= 'A' && ch <= 'Z') ||
		 (ch >= '0' && ch <= '9') ||
		 (ch == '_') || (ch == '-')) {
		   buf.append(ch);
		   nextCh();
	  }
	  chars = buf.toString();

	  chars match {
	    // for methods
	    case "doc"      => return DOC;
	    case "document" => return DOCUMENT;
	    case "text"     => return TEXT;
	    case "min"      => return MIN;
	    case "max"      => return MAX;
	    case "count"    => return COUNT;
	    case "sum"      => return SUM;
	    case "avg"      => return AVG;
            case "not"      => return NOT;
	    case "empty"    => return EMPTY;
	    case "exists"   => return EXISTS;
	    case "position" => return POSITION;
	    case "contains" => return CONTAINS;
	    case "starts-with"     => return STARTSWITH;
	    case "ends-with"       => return ENDSWITH;
	    case "distinct-values" => return DISTINCT;

	    // for operations
	    case "div"  => return DIV;
	    case "idiv" => return DIV;
	    case "mod"  => return MOD;
	    case "and"  => return AND;
	    case "or"   => return OR;
	    
	    // for keywords
	    case "for"        => return FOR;
	    case "let"        => return LET;
	    case "where"      => return WHERE;
	    case "order"      => return ORDER;
	    case "by"         => return BY;
	    case "return"     => return RETURN;
	    case "if"         => return IF;
	    case "then"       => return THEN;
	    case "else"       => return ELSE;
	    case "every"      => return EVERY;
	    case"some"        => return SOME;
	    case "in"         => return IN;
	    case "satisfies"  => return SATISFIES;
	    case "ascending"  => return ASCENDING;
	    case "descending" => return DESCENDING;
	    
	    // for identifiers
	    case _            => return IDENT;
	  }
    }   
    // for all numbers
    else if (ch >= '1' && ch <= '9') {
      buf.setLength(0);
      buf.append(ch);
      nextCh();
      while (ch >= '0' && ch <= '9') {
        buf.append(ch);
        nextCh();
      }
      chars = buf.toString();
      return NUMBER;
    }
    // other symbols and characters
    else {
      ch match {
        // for number 0
        case '0' =>
          buf.setLength(0);
          buf.append(ch);
          chars = buf.toString();
          nextCh();
          return NUMBER;

        // for strings
        case '\"' =>
          nextCh();         
          if (ch == '{')
            return QUOT;
          else if (stringflag) {
            buf.setLength(0);
            while (ch >= ' ' && ch != EOF_CH && ch != '\"') {
              buf.append(ch);
              nextCh();
            }
            if (ch == '\"') { // end of string
              nextCh();
              chars = buf.toString();
              return STRING;
            }
            else {
              Report.error(start, "unclosed string"); // error
              return BAD;
            }
          }
          else
            return QUOT;

        // for operations
        case '+' =>
          nextCh();
          return PLUS;
        case '-' =>
          nextCh();
          return MINUS;
        case '*' =>
          nextCh();
          return TIMES;
        case '=' =>
          nextCh();
          return EQUAL;

        case ':' =>
          nextCh();
          if (ch == '=') {
            nextCh();
            return ASSIGN;
          }
	  else
	    return BAD;

        case '!' =>
          nextCh();
          if (ch == '=') {
            nextCh();
            return NOTEQUAL;
          }
	  else
	    return BAD;

        case '<' =>
          nextCh();
          if (ch == '=') {
            nextCh();
            return LETHAN;
          }
          else {
            return LTHAN;
          }

        case '>' =>
          nextCh();
          if (ch == '=') {
            nextCh();
            return GETHAN;
          }
          else {
            return GTHAN;
          }

        case '(' =>
          nextCh();
          return LPAREN;
        case ')' =>
          nextCh();
          return RPAREN;
        case '{' =>
          nextCh();
          return LACOLAD;
        case '}' =>
          nextCh();
	  if (ch == '"')
	    stringflag = false;
          return RACOLAD;
        case '[' =>
          nextCh();
          return LBRACKET;
        case ']' =>
          nextCh();
          return RBRACKET;
        case '$' =>
          nextCh();
          return DOLLAR;
        case '@' =>
          nextCh();
          return ATSIGN;

        case '/' =>
          nextCh();
          if (ch == '/') {
            nextCh();
            return DSLASH;
          }
          else {
            return SLASH;
          }

        case ',' =>
          nextCh();
          return COMMA;

        case EOF_CH =>
          nextCh();
          return EOF;

        // for the characters that don't exist in misc
	case _ =>
          nextCh();
          return BAD;
      }
    }
  }

  /** Return a textual representation of the current token.
   */
  def representation():String = {
    var representation:String = tokenClass(token);
    if (token == NUMBER || token == IDENT || token == STRING)
      representation = representation + "(" + chars + ")";
    return representation;
  }

  /** Return the textual representation of the given token class.
   */
  def tokenClass(token:int):String = {
    token match {
      case EOF        => return "<eof>";
      case BAD        => return "<bad>";
      case IDENT      => return "ident";
      case NUMBER     => return "number";
      case STRING     => return "string";
      case DOC        => return "doc";
      case DOCUMENT   => return "document";
      case MIN        => return "min";
      case MAX        => return "max";
      case COUNT      => return "count";
      case SUM        => return "sum";
      case AVG        => return "avg";
      case NOT        => return "not";
      case EMPTY      => return "empty";
      case EXISTS     => return "exists";
      case DISTINCT   => return "distinct-values";
      case POSITION   => return "position";
      case CONTAINS   => return "contains";
      case STARTSWITH => return "starts-with";
      case ENDSWITH   => return "ends-with";
      case TEXT       => return "text";
      case PLUS       => return "+";
      case MINUS      => return "-";
      case TIMES      => return "*";
      case DIV        => return "div";
      case IDIV       => return "idiv";
      case MOD        => return "mod";
      case ASSIGN     => return ":=";
      case EQUAL      => return "=";
      case NOTEQUAL   => return "!=";
      case LTHAN      => return "<";
      case GTHAN      => return ">";
      case LETHAN     => return "<=";
      case GETHAN     => return ">=";
      case AND        => return "and";
      case OR         => return "or";

      case LPAREN     => return "(";
      case RPAREN     => return ")";
      case LBRACKET   => return "[";
      case RBRACKET   => return "]";
      case LACOLAD    => return "{";
      case RACOLAD    => return "}";
      case DOLLAR     => return "$";
      case ATSIGN     => return "@";
      case SLASH      => return "/";
      case DSLASH     => return "//";
      case COMMA      => return ",";
      case QUOT       => return "\"";

      case FOR        => return "for";
      case LET        => return "let";
      case WHERE      => return "where";
      case ORDER      => return "order";
      case BY         => return "by";
      case RETURN     => return "return";
      case IF         => return "if";
      case THEN       => return "then";
      case ELSE       => return "else";
      case EVERY      => return "every";
      case SOME       => return "some";
      case IN         => return "in";
      case SATISFIES  => return "satisfies";
      case ASCENDING  => return "ascending";
      case DESCENDING => return "descending";
      case _          =>
	throw new Error("unknown token class " + token);
    }
  }

  /** Puts the next character into 'ch' and updates the current position.
   */
  def nextCh():Unit = {
    ch match {
      case EOF_CH => 
	return; 
      case '\n'   =>
        column = 1;
	line = line + 1;
      case _      =>
        column = column + 1;
    }
    try {
      ch = (in.read()).asInstanceOf[char];
      if ((oldch == '\r') && (ch == '\n'))
	oldch = (in.read()).asInstanceOf[char];
      else
	oldch = ch;
      if (oldch == '\r')
	ch = '\n';
      else
	ch = oldch;
    } catch {
      case e: IOException =>
	Report.fail(Position.encode(line, column), e.getMessage());
    }
  }

  var oldch:char = ' ';

}
